﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ALDIOrganizer
{ 
    /// <summary>
    /// Contains the actual Rota sheet (Shifts and contract_hours), serves as a "sheet" for the RotaPackaging class
    /// </summary>
    /// <remarks>
    /// Sheet doesn't store the information about _staff - for example. It is able to "find out"
    /// who works On
    /// Sheet state is used as a snapshot for Undo/Redo functionality.
    /// </remarks>
    [Serializable]
    public partial class Sheet : IModifiable
    {
        internal List<Cell[]> Cells;
        
        [NonSerialized]
        internal TopShiftsTracker Tracker;
        
        [NonSerialized]
        bool _onModifiedChangedSuppressed;
        internal void SuppressModifiedChanged()
        {
            _onModifiedChangedSuppressed = true;
        }
        internal void ResumeModifiedChanged()
        {
            _onModifiedChangedSuppressed = false;
        }

        [NonSerialized]
        private bool _modified;
        public bool Modified
        {
            get
            {
                return _modified;
            }
            set
            {
                if (_onModifiedChangedSuppressed ||
                    value == _modified)
                {
                    return;
                }                
                _modified = value;
                if (OnModifiedChanged != null)
                {
                    OnModifiedChanged(value);
                }
            }
        }

        internal bool AnyOddShifts
        {
            get
            {
                return (from shift in GetAllShifts
                        where shift.MarkedAsOdd
                        select shift).Any();
            }
        }
        public delegate void OnModifiedChangedEventHandler(bool pNewValue);
        public delegate bool OnLeavingModifiedEntityEventHandler();
        public event OnModifiedChangedEventHandler OnModifiedChanged;       
                        
        internal SheetType Mode;
                
        internal bool IsTemplate
        {
            get
            {
                return this is Template;
            }
        }

        internal bool IsRota
        {
            get
            {
                return this is Rota; 
            }
        }
                
        public delegate void OnDataChangedEventHandler(RowTag[] rowtags, Weekday[] days);        
        public virtual event OnDataChangedEventHandler OnDataChanged;

        public delegate void OnCellChangedEventHandler(Cell pCell);
        public event OnCellChangedEventHandler OnCellValueChanged;
        public event OnCellChangedEventHandler OnCellStyleChanged;
                
        

        /// <summary>
        /// Initializes Tracker field and repopulates the Tracker with all Shifts contained in Sheet
        /// </summary>
        /// <remarks>
        /// Tracker is not serialized When the application makes a Copy of Sheet instance;
        /// so When this Copy (from Undo buffer, for example) is re-imported, we will find that
        /// its Tracker field is null.
        /// I could have just serialized Tracker, maybe it'pShift simplier, but I didn't want to serialize
        /// redundant data (which it obviously is) out of principle.
        /// </remarks>
        internal void ResetTracker()
        {
            Tracker = new TopShiftsTracker();
            Tracker.ImportData(this);
        }

        internal void ActivateCellStyles()
        {
            if (IsRota)
            {
                foreach (var cell in (this as Rota).GetAllCells)
                {
                    cell.ActivateAndUpdate();
                }
            }
            if (!IsTemplate)
            {
                return;
            }
            foreach (var cell in (this as Template).GetAllCells)
            {
                cell.ActivateAndUpdate();
            }
        }

        #region constructors
        public Sheet()
        {
            Cells = new List<Cell[]>();
            Tracker = new TopShiftsTracker();
            ResumeModifiedChanged();
        }        
        #endregion

        

        #region columns, rows - counting, enumerating etc.
        public const int cols = 8; // how many "columns" does Cells have? fixed
        public int rows
        {
            get
            {                
                return Cells == null ? 0 : Cells.Count;
            }
        }
                

        public IEnumerable<Cell[]> GetAllRows
        {
            get
            {
                for (int i = 0; i < rows; i++)
                    yield return this.Cells[i];
            }
        }

        
        #endregion

        #region GetShifts... methods

        public IEnumerable<Shift> GetAllShifts
        {
            get 
            {
                return from row in GetAllRows
                       from cell in row
                       from shift in cell.Shifts.ToArray()
                       select shift;
            }
        }
        
        public static List<Shift> GetAllShiftsForRow(Cell[] row)
        {
            var result = new List<Shift>();
            for (var day = 1; day <= 7; day++)
            {
                result.AddRange(row[day].Shifts.ToArray());
            }
            return result;
        }

        public List<Shift> GetAllShiftsForColumn(int col)
        {
            var result = new List<Shift>();
            foreach (var row in GetAllRows)
            {
                result.AddRange(row[col].Shifts.ToArray());
            }
            return result;
        }        

        public List<Shift> GetShiftsForRowTaggedWith(RowTag tag)
        {
            var rowInQuestion = this[tag];
            return GetAllShiftsForRow(rowInQuestion);
        }

        public void ClearAllShifts()
        {
            foreach (var cell in GetAllRows.SelectMany(row => row))
            {
                cell.ClearShifts();
            }
        }

        public RowTag[] GetAllTags
        {
            get
            {
                return (from row in Cells
                        select row[0].Tag).ToArray();
            }
        }


        

        
        #endregion

        public SumOfHours[] TotalHours()
        {
            var result = new SumOfHours[cols];
            if (rows == 0)
            {
                return result;
            }
            
            foreach (var s in GetAllRows.SelectMany(GetAllShiftsForRow))
            {
                result[s.Day.ToInt0()].OnProductivity += s.Length().GetTotal; // from 0 to 6 (mon-sun)
                result[cols - 1] += s.Length(); // always 7 (GetTotal)
            }

            return result;
        }

        public static Cell[] SpawnNewRow(SheetType mode, Sheet parent, RowTag tag)
        {
            var row = new Cell[cols];
            for (var i = 0; i < cols; i++)
            {
                row[i] = new Cell(parent, i, tag);
                row[i].Update();           
            }
            return row;
        }

        public void AddRow(Cell[] row)
        {
            Cells.Add(row);
        }


        public void TriggerOnCellValueChangedEvent(Cell pCell)
        {
            if (OnCellValueChanged != null)
            {
                OnCellValueChanged(pCell);
            }
        }

        public void TriggerOnCellStyleChangedEvent(Cell pCell)
        {
            if (OnCellStyleChanged != null)
            {
                OnCellStyleChanged(pCell);
            }
        }

        internal virtual void addShifts(ShiftTandem shifts, Weekday day, RowTag rowtag, bool refreshAndClick)
        {            
            foreach (Shift s in shifts)
            {
                s.Day = day;
                switch (rowtag.MyType)
                {
                    case RowTagType.LongInt: s.EmployeeID = rowtag.Int64Value; break;
                    case RowTagType.GUID: s.JobPosition = this[rowtag.GuidValue][0].RoleValue; break;                        
                    default: break;
                }                                                
                addShift(day, rowtag, s);
            }

            // _shallWeUpdate = true;

            var cell = this[rowtag][day.ToInt1()];
            if (!refreshAndClick)
            {
                return;
            }
            cell.Update();                                                
            Sounds.Play(Sounds.Validated);
        }        

        protected void addShift(Weekday day, RowTag rowtag, Shift pShift)
        {
            var row_in_question = this[rowtag];
            var cell_in_question = row_in_question[day.ToInt1()];
            if (!cell_in_question.IsEditable)
            {
                return;
            }

            pShift.Parent = this;
            Tracker.Add(pShift);
            cell_in_question.AddShift(pShift);
            Modified = true;
        }
        
        protected void removeShifts(Cell cell)
        {
            if (cell.MyCellType != CellType.Shift)
            {
                return;
            }
            foreach (Shift shift in cell.Shifts)
            {
                Tracker.Remove(shift);
            }
            Modified = true;
            cell.ClearShifts();
            cell.Notes = String.Empty;            
        }


        internal virtual void RefreshAfterDragDrop(RowTag[] pTags, Weekday[] pDays)
        {            
            OnDataChanged(pTags, pDays);
        }                                
        

        internal void ClearColumn(Weekday day)
        {
            if (Mode == SheetType.Rota)
            {
                var tags = new List<RowTag>();
                foreach (var row in GetAllRows)
                {
                    tags.Add(row[0].Tag);
                    (this as Rota).RemoveShifts(day, row[0].Tag, false);                    
                }
                (this as Rota).ColumnDataChanged(day);                
                return;
            }
            
            // if (Mode == SheetType.Template)
            foreach (var row in GetAllRows)
            {
                (this as Template).RemoveShifts(day, row[0].Tag, false);
            }
        }

        internal void RefreshColumn(Weekday day)
        {
            foreach (var row in GetAllRows)
            {
                row[day.ToInt1()].Update();
            }            
        }

        public bool OKLeaving()
        {
            if (Modified == false)
            {
                return true;
            }
            if (OnLeavingModifiedEntity != null)
            {
                return OnLeavingModifiedEntity();
            }
            return true;
        }
        #region IModifiable Members


        public event OnLeavingModifiedEntityEventHandler OnLeavingModifiedEntity;

        #endregion
    }
}
